/*
 *  linux/init/main.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__

#include "fcntl.h"
#include "time.h"
#include "linux/tty.h"
#include "linux/sched.h"
#include "asm/system.h"
#include "stdarg.h"
#include "cmos.h"
#include "unistd.h"

/*
 * we need this inline - forking from kernel space will result
 * in NO COPY ON WRITE (!!!), until an execve is executed. This
 * is no problem, but for the stack. This is handled by not letting
 * main() use the stack at all after fork(). Thus, no function
 * calls - which means inline code for fork too, as otherwise we
 * would use the stack upon exit from 'fork()'.
 *
 * Actually only pause and fork are needed inline, so that there
 * won't be any messing with the stack from main(), but we define
 * some others too.
 */
inline _syscall0(int, fork);

inline _syscall0(int, pause);

static inline _syscall1(int, setup, void *, BIOS);

inline _syscall0(int, sync);

static char printbuf[1024];

extern char *strcpy();

extern int vsprintf();

extern void init(void);

extern void blk_dev_init(void);

extern void chr_dev_init(void);

extern void hd_init(void);

extern void floppy_init(void);

extern void mem_init(long start, long end);

extern long rd_init(long mem_start, int length);

extern long kernel_mktime(struct tm *tm);

static int sprintf(char *str, const char *fmt, ...) {
    va_list args;
    int i;

    va_start(args, fmt);
    i = vsprintf(str, fmt, args);
    va_end(args);
    return i;
}



/*
 * This is set up by the setup-routine at boot-time
 */
//扩展内存的信息，它是指1M以后的扩展内存大小，是在setup.s 中使用$0x15中断获取，并保存0x90002地址处
#define EXT_MEM_K (*(unsigned short *)0x90002)
#define CON_ROWS ((*(unsigned short *)0x9000e) & 0xff)
#define CON_COLS (((*(unsigned short *)0x9000e) & 0xff00) >> 8)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
#define ORIG_SWAP_DEV (*(unsigned short *)0x901FA)


#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)

static void time_init(void) {
    struct tm time;

    do {
        time.tm_sec = CMOS_READ(0);
        time.tm_min = CMOS_READ(2);
        time.tm_hour = CMOS_READ(4);
        time.tm_mday = CMOS_READ(7);
        time.tm_mon = CMOS_READ(8);
        time.tm_year = CMOS_READ(9);
    } while (time.tm_sec != CMOS_READ(0));
    BCD_TO_BIN(time.tm_sec);
    BCD_TO_BIN(time.tm_min);
    BCD_TO_BIN(time.tm_hour);
    BCD_TO_BIN(time.tm_mday);
    BCD_TO_BIN(time.tm_mon);
    BCD_TO_BIN(time.tm_year);
    time.tm_mon--;
    startup_time = kernel_mktime(&time);
}

static long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;

static char term[32];

static char *argv_rc[] = {"/bin/sh", NULL};
static char *envp_rc[] = {"HOME=/", NULL, NULL};

static char *argv[] = {"-/bin/sh", NULL};
static char *envp[] = {"HOME=/usr/root", NULL, NULL};


struct drive_info {
    char dummy[32];
} drive_info;

int main(void)        /* This really IS void, no error here. */
{            /* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */

    ROOT_DEV = ORIG_ROOT_DEV;
    SWAP_DEV = ORIG_SWAP_DEV;
    sprintf(term, "TERM=con%dx%d", CON_COLS, CON_ROWS);
    envp[1] = term;
    envp_rc[1] = term;
    drive_info = DRIVE_INFO; //复制0x90080处的硬盘参数表
    memory_end = (1 << 20) + (EXT_MEM_K << 10); //内存大小 = 1M + 扩展内存 * 1024
    memory_end &= 0xfffff000; //忽略不到4Kb(1页）的内存数。
    //根据内存的大小，来设置buffer_memory_end的末端位置，
    // todo buffer_memory_end是指用于和磁盘缓冲的区域
    if (memory_end > 16 * 1024 * 1024)
        memory_end = 16 * 1024 * 1024;
    if (memory_end > 12 * 1024 * 1024)
        buffer_memory_end = 4 * 1024 * 1024;
    else if (memory_end > 6 * 1024 * 1024)
        buffer_memory_end = 2 * 1024 * 1024;
    else
        buffer_memory_end = 1 * 1024 * 1024;
    //缓冲区之后的内存，用于为进程分配使用
    main_memory_start = buffer_memory_end;
#ifdef RAMDISK
    main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
    mem_init(main_memory_start, memory_end);
    trap_init();  //设置中断向量表
    blk_dev_init(); //初始化块设备的读写请求队列
    chr_dev_init(); // 字符设备初始化。
    tty_init();
    time_init();
    sched_init(); // 调度程序初始化（加载任务0的 tr, ldtr)
    buffer_init(buffer_memory_end); //硬盘的高速缓冲管理初始化，建内存链表等。
    hd_init(); // 硬盘初始化。
    floppy_init(); // 软驱初始化。
    sti();  // 所有初始化工作都做完了，开启中断。
    move_to_user_mode();
    if (!fork()) {        /* we count on this going ok */
        init();
    }
/*
 *   NOTE!!   For any other task 'pause()' would mean we have to get a
 * signal to awaken, but task0 is the sole exception (see 'schedule()')
 * as task 0 gets activated at every idle moment (when no other tasks
 * can run). For task0 'pause()' just means we go check if some other
 * task can run, and if not we return here.
 */
    for (;;)
            __asm__("int $0x80"::"a" (__NR_pause));
}

int printf(const char *fmt, ...) {
    va_list args;
    int i;

    va_start(args, fmt);
    write(1, printbuf, i = vsprintf(printbuf, fmt, args));
    va_end(args);
    return i;
}


void init(void) {
    int pid, i;

    // 该调用触发软中断，
    // 调用到->system_call.s system_call-> call *sys_call_table(,%eax,4) -> sys_call_table 表中索引0的函数->  hd.c sys_setup()
    setup((void *) &drive_info);
    (void) open("/dev/tty1", O_RDWR, 0);
    (void) dup(0);
    (void) dup(0);
    printf("%d buffers = %d bytes buffer space\n\r", NR_BUFFERS,
           NR_BUFFERS * BLOCK_SIZE);
    printf("Free mem: %d bytes\n\r", memory_end - main_memory_start);
    if (!(pid = fork())) {
        close(0);
        if (open("/etc/rc", O_RDONLY, 0))
            _exit(1);
        execve("/bin/sh", argv_rc, envp_rc);
        _exit(2);
    }
    if (pid > 0)
        while (pid != wait(&i))
            /* nothing */;
    while (1) {
        if ((pid = fork()) < 0) {
            printf("Fork failed in init\r\n");
            continue;
        }
        if (!pid) {
            close(0);
            close(1);
            close(2);
            setsid();
            (void) open("/dev/tty1", O_RDWR, 0);
            (void) dup(0);
            (void) dup(0);
            _exit(execve("/bin/sh", argv, envp));
        }
        while (1)
            if (pid == wait(&i))
                break;
        printf("\n\rchild %d died with code %04x\n\r", pid, i);
        sync();
    }
    _exit(0);    /* NOTE! _exit, not exit() */
}